package lt.marsrutai.mobile.android.dao;

import java.io.IOException;
import java.util.List;

import lt.marsrutai.mobile.android.dao.dto.ArrivalTimeDto;
import lt.marsrutai.mobile.android.dao.dto.BusRunDto;
import lt.marsrutai.mobile.android.dao.dto.BusStopDto;
import lt.marsrutai.mobile.android.dao.dto.BusTrackDto;
import lt.marsrutai.mobile.android.dao.dto.DaysOfWeekDto;
import lt.marsrutai.mobile.android.dao.dto.ScheduleDto;
import lt.marsrutai.mobile.android.dao.dto.ScopeInfoDto;
import lt.marsrutai.mobile.android.dao.dto.SearchResultDto;
import lt.marsrutai.mobile.android.dao.dto.StopDayTimesDto;
import lt.marsrutai.mobile.android.dao.dto.TransportDto;
import lt.marsrutai.mobile.android.dao.exception.BackendException;
import lt.marsrutai.mobile.android.dao.exception.EmptyResponseException;
import lt.marsrutai.mobile.android.dao.utils.DtoFiller;
import lt.marsrutai.mobile.android.util.HttpGetRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class OnlineJsonDataDao implements DataDao, APIFunctionConstants {

    private DtoFiller dtoFiller;
    private DaoExceptionMapper exceptionMapper;

    public OnlineJsonDataDao() {
        dtoFiller = new DtoFiller();
        exceptionMapper = new DaoExceptionMapper();
    }

    private <T> T execute(DaoBody<T> daoBody) throws BackendException {
        try {
            return daoBody.execute();
        }
        catch (Exception ex) {
            throw exceptionMapper.map(ex);
        }
    }

    private abstract class DaoBody<T> {

        protected abstract T execute() throws JSONException, IOException;
    }

    private JSONArray downloadAsJSONArray(String uri) throws JSONException, IOException {
        String data = new HttpGetRequest(uri).download();

        if (null == data || data.length() == 0) {
            throw new EmptyResponseException(uri);
        }

        return new JSONArray(data);
    }

    private JSONObject downloadAsJSONObject(String uri) throws JSONException, IOException {
        String data = new HttpGetRequest(uri).download();

        if (null == data || data.length() == 0) {
            throw new EmptyResponseException(uri);
        }

        return new JSONObject(data);
    }

    @Override
    public List<ScopeInfoDto> getAvailableMobileScopes() throws BackendException {
        return execute(new DaoBody<List<ScopeInfoDto>>() {

            @Override
            protected List<ScopeInfoDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_AVAILABLE_MOBILE_SCOPES).toString());
                return dtoFiller.fillList(FUNCTION_GET_AVAILABLE_MOBILE_SCOPES, ScopeInfoDto.class, jsonArray);
            }
        });
    }

    @Override
    public ScopeInfoDto getScopeInfo(final String scopeFolder) throws BackendException {
        return execute(new DaoBody<ScopeInfoDto>() {

            @Override
            protected ScopeInfoDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_SCOPE_INFO).scope(scopeFolder).toString());
                return dtoFiller.fillObject(FUNCTION_GET_SCOPE_INFO, ScopeInfoDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<String> getStopNames(final String scopeFolder) throws BackendException {
        return execute(new DaoBody<List<String>>() {

            @Override
            protected List<String> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_NAMES).scope(scopeFolder).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_NAMES, String.class, jsonArray);
            }
        });
    }

    @Override
    public List<BusStopDto> getStops(final String scopeFolder, final boolean withSchedules, final String searchQuery, final Double lat,
            final Double lng, final Double distance) throws BackendException {
        return execute(new DaoBody<List<BusStopDto>>() {

            @Override
            protected List<BusStopDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOPS).scope(scopeFolder)
                        .param(PARAM_WITH_SCHEDULES, withSchedules).param(PARAM_FILTER_NAME, searchQuery).param(PARAM_FILTER_LAT, lat).param(PARAM_FILTER_LNG, lng).param(PARAM_FILTER_DISTANCE, distance).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOPS, BusStopDto.class, jsonArray);
            }
        });
    }

    @Override
    public BusStopDto getStopById(final String scopeFolder, final String stopId) throws BackendException {
        return execute(new DaoBody<BusStopDto>() {

            @Override
            protected BusStopDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_STOP_BY_ID).scope(scopeFolder)
                        .param(PARAM_STOP_ID, stopId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_STOP_BY_ID, BusStopDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<ArrivalTimeDto> getArrivalTimes(final String scopeFolder, final String stopId, final String stopGroupName, final int maxMinutes,
            final int maxResults, final String time) throws BackendException {
        return execute(new DaoBody<List<ArrivalTimeDto>>() {

            @Override
            protected List<ArrivalTimeDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_ARRIVAL_TIMES).scope(scopeFolder)
                        .param(PARAM_STOP_FULL_NAME, stopGroupName)
                        .param(PARAM_STOP_ID, stopId)
                        .param(PARAM_MAX_MINUTES, maxMinutes)
                        .param(PARAM_MAX_RESULTS, maxResults)
                        .param(PARAM_TIME, time).toString());
                return dtoFiller.fillList(FUNCTION_GET_ARRIVAL_TIMES, ArrivalTimeDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<ScheduleDto> getSchedules(final String scopeFolder, final String transport) throws BackendException {
        return execute(new DaoBody<List<ScheduleDto>>() {

            @Override
            protected List<ScheduleDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_SCHEDULES).scope(scopeFolder)
                        .param(PARAM_TRANSPORT, transport).toString());
                return dtoFiller.fillList(FUNCTION_GET_SCHEDULES, ScheduleDto.class, jsonArray);
            }
        });
    }

    @Override
    public ScheduleDto getScheduleById(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<ScheduleDto>() {

            @Override
            protected ScheduleDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_SCHEDULE_BY_ID).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_SCHEDULE_BY_ID, ScheduleDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<BusTrackDto> getTracks(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<List<BusTrackDto>>() {

            @Override
            protected List<BusTrackDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_TRACKS).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillList(FUNCTION_GET_TRACKS, BusTrackDto.class, jsonArray);
            }
        });
    }

    @Override
    public BusTrackDto getTrackById(final String scopeFolder, final String trackId) throws BackendException {
        return execute(new DaoBody<BusTrackDto>() {

            @Override
            protected BusTrackDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_TRACK_BY_ID).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_TRACK_BY_ID, BusTrackDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<BusStopDto> getTrackStops(final String scopeFolder, final String trackId) throws BackendException {
        return execute(new DaoBody<List<BusStopDto>>() {

            @Override
            protected List<BusStopDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_TRACK_STOPS).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).toString());
                return dtoFiller.fillList(FUNCTION_GET_TRACK_STOPS, BusStopDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<DaysOfWeekDto> getDifferentRunningDays(final String scopeFolder, final String scheduleId) throws BackendException {
        return execute(new DaoBody<List<DaysOfWeekDto>>() {

            @Override
            protected List<DaysOfWeekDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_DIFFERENT_RUNNING_DAYS).scope(scopeFolder)
                        .param(PARAM_SCHEDULE_ID, scheduleId).toString());
                return dtoFiller.fillList(FUNCTION_GET_DIFFERENT_RUNNING_DAYS, DaysOfWeekDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<ArrivalTimeDto> getStopTimes(final String scopeFolder, final String trackId, final String stopId, final int day)
            throws BackendException {
        return execute(new DaoBody<List<ArrivalTimeDto>>() {

            @Override
            protected List<ArrivalTimeDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_TIMES).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).param(PARAM_STOP_ID, stopId).param(PARAM_DAY, day).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_TIMES, ArrivalTimeDto.class, jsonArray);
            }
        });
    }

    @Override
    public List<StopDayTimesDto> getStopDayTimes(final String scopeFolder, final String trackId, final String stopId)
            throws BackendException {
        return execute(new DaoBody<List<StopDayTimesDto>>() {

            @Override
            protected List<StopDayTimesDto> execute() throws JSONException, IOException {
                JSONArray jsonArray = downloadAsJSONArray(new ApiRequest(FUNCTION_GET_STOP_DAY_TIMES).scope(scopeFolder)
                        .param(PARAM_TRACK_ID, trackId).param(PARAM_STOP_ID, stopId).toString());
                return dtoFiller.fillList(FUNCTION_GET_STOP_DAY_TIMES, StopDayTimesDto.class, jsonArray);
            }
        });
    }

    @Override
    public SearchResultDto findRoutes(final String scopeFolder, final String start, final String end, final String time,
            final boolean isArrival) throws BackendException {
        return execute(new DaoBody<SearchResultDto>() {

            @Override
            protected SearchResultDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_FIND_ROUTES).scope(scopeFolder)
                        .param(PARAM_START, start).param(PARAM_END, end).param(PARAM_TIME, time).param(PARAM_IS_ARRIVAL, isArrival)
                        .toString());
                return dtoFiller.fillObject(FUNCTION_FIND_ROUTES, SearchResultDto.class, jsonObject);
            }
        });
    }

    @Override
    public BusRunDto getRunById(final String scopeFolder, final String runId) throws BackendException {
        return execute(new DaoBody<BusRunDto>() {

            @Override
            protected BusRunDto execute() throws JSONException, IOException {
                JSONObject jsonObject = downloadAsJSONObject(new ApiRequest(FUNCTION_GET_RUN_BY_ID).scope(scopeFolder)
                        .param(PARAM_RUN_ID, runId).toString());
                return dtoFiller.fillObject(FUNCTION_GET_RUN_BY_ID, BusRunDto.class, jsonObject);
            }
        });
    }

    @Override
    public List<TransportDto> getTransports(String scopeName) {
        throw new RuntimeException("Uniplmenented. Transports should be retrieved from local storage.");
    }
}
